package security

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
)

// CryptoManager handles encryption and decryption operations
type CryptoManager struct {
	gcm cipher.AEAD
}

// NewCryptoManager creates a new CryptoManager with a randomly generated key
func NewCryptoManager() (*CryptoManager, error) {
	key := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return nil, fmt.Errorf("failed to generate key: %w", err)
	}
	return NewCryptoManagerWithKey(key)
}

// NewCryptoManagerWithKey creates a new CryptoManager with the provided key
func NewCryptoManagerWithKey(key []byte) (*CryptoManager, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	return &CryptoManager{
		gcm: gcm,
	}, nil
}

// GenerateKey generates a random 32-byte key
func GenerateKey() ([]byte, error) {
	key := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return nil, fmt.Errorf("failed to generate key: %w", err)
	}
	return key, nil
}

// Encrypt encrypts the provided data
func (cm *CryptoManager) Encrypt(data []byte) (string, error) {
	nonce := make([]byte, cm.gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	encrypted := cm.gcm.Seal(nonce, nonce, data, nil)
	return base64.StdEncoding.EncodeToString(encrypted), nil
}

// Decrypt decrypts the provided data
func (cm *CryptoManager) Decrypt(encryptedStr string) ([]byte, error) {
	encrypted, err := base64.StdEncoding.DecodeString(encryptedStr)
	if err != nil {
		return nil, fmt.Errorf("failed to decode base64: %w", err)
	}

	if len(encrypted) < cm.gcm.NonceSize() {
		return nil, fmt.Errorf("invalid ciphertext length")
	}

	nonce := encrypted[:cm.gcm.NonceSize()]
	ciphertext := encrypted[cm.gcm.NonceSize():]

	plaintext, err := cm.gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt: %w", err)
	}

	return plaintext, nil
}

// EncryptString encrypts a string and returns the base64-encoded result
func (cm *CryptoManager) EncryptString(data string) (string, error) {
	return cm.Encrypt([]byte(data))
}

// DecryptString decrypts a base64-encoded string
func (cm *CryptoManager) DecryptString(encryptedStr string) (string, error) {
	decrypted, err := cm.Decrypt(encryptedStr)
	if err != nil {
		return "", err
	}
	return string(decrypted), nil
}
